/*
 * Copyright 2019-2025 the original author or authors.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.iiifi.kite.boot.web.servlet.error;

import java.time.LocalDateTime;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.DispatcherServlet;

import com.iiifi.kite.alarm.event.KiteErrorEvent;
import com.iiifi.kite.boot.utils.WebUtils;
import com.iiifi.kite.core.exception.ServiceException;
import com.iiifi.kite.core.result.Result;
import com.iiifi.kite.core.result.SystemCode;
import com.iiifi.kite.core.util.Exceptions;
import com.iiifi.kite.core.util.ObjectUtils;
import com.iiifi.kite.core.util.StringPool;
import com.iiifi.kite.core.util.StringUtils;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 未知异常转译和发送，方便监听，对未知异常统一处理。Order 排序优先级低
 *
 * @author kite@iiifi.com 花朝
 */
@Slf4j
@Order
@Configuration
@RestControllerAdvice
@AllArgsConstructor
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class KiteExceptionTranslator {
    private final ApplicationEventPublisher publisher;

    @ExceptionHandler(ServiceException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<Object> handleError(ServiceException e) {
        log.error("业务异常", e);
        Result<Object> result = e.getResult();
        if (result == null) {
            // 发送：未知业务异常事件
            result = Result.fail(SystemCode.FAILURE, e.getMessage());
            publishEvent(e);
        }
        return result;
    }

    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<Object> handleError(Throwable e) {
        log.error("未知异常", e);
        // 发送：未知异常异常事件
        publishEvent(e);
        return Result.fail(SystemCode.FAILURE);
    }

    private void publishEvent(Throwable error) {
        KiteErrorEvent event = new KiteErrorEvent();
        HttpServletRequest request = WebUtils.getRequest();
        // 请求方法名
        event.setRequestMethod(request.getMethod());
        // 拼接地址
        String requestUrl = request.getRequestURI();
        String queryString = request.getQueryString();
        if (StringUtils.isNotBlank(queryString)) {
            requestUrl = requestUrl + StringPool.QUESTION_MARK + queryString;
        }
        event.setRequestUrl(requestUrl);
        // 堆栈信息
        event.setStackTrace(Exceptions.getStackTraceAsString(error));
        event.setExceptionName(error.getClass().getName());
        event.setMessage(error.getMessage());
        event.setCreatedAt(LocalDateTime.now());
        StackTraceElement[] elements = error.getStackTrace();
        if (ObjectUtils.isNotEmpty(elements)) {
            // 报错的类信息
            StackTraceElement element = elements[0];
            event.setClassName(element.getClassName());
            event.setFileName(element.getFileName());
            event.setMethodName(element.getMethodName());
            event.setLineNumber(element.getLineNumber());
        }
        // 发布事件
        publisher.publishEvent(event);
    }
}
